package com.wiscamp.ninechapters.accounts.domain.services;

import com.wiscamp.ninechapters.accounts.domain.models.Account;
import com.wiscamp.ninechapters.accounts.domain.models.AccountBatch;
import com.wiscamp.ninechapters.accounts.domain.models.AccountBatchLog;
import com.wiscamp.ninechapters.accounts.domain.models.AccountLog;
import com.wiscamp.ninechapters.accounts.domain.repositories.AccountsRepository;
import com.wiscamp.ninechapters.accounts.enums.AccountTypeEnum;
import com.wiscamp.ninechapters.common.GlobalState;
import cube.ddd.aspect.AopServiceException;
import com.wiscamp.ninechapters.common.core.DateTimeHelper;
import com.wiscamp.ninechapters.common.exception.ServiceException;
import com.wiscamp.ninechapters.ddd.DomainService;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Objects;

@Service
public class AccountService implements DomainService {

    @Autowired
    private AccountsRepository accountRepository;

    @Autowired
    public AccountService(AccountsRepository accountRepository) {
        this.accountRepository = accountRepository;
    }


    /**
     * gets a account
     * accountType 100 = Haibei
     *
     * @param accountType
     * @param userId
     * @return
     */
    @AopServiceException
    public Account getAccountByType(@Min(0) int accountType, @Min(0) long userId) {
        var oldAccount = accountRepository.getAccountByType(accountType, userId);
        if (Objects.nonNull(oldAccount))
            return oldAccount;
        else {
            // 未找到此用户同类型账户, 创建一个新专户
            var newAccount = new Account();
            newAccount.setAccountType(accountType);
            newAccount.setUserId(userId);
            // 默认积分为批次管理模式
            newAccount.setCreateTime(LocalDateTime.now());
            newAccount.setEditTime(LocalDateTime.now());
            if (accountType == AccountTypeEnum.POINTS.getValue())
                newAccount.setBatchManagement(true);
            return accountRepository.saveAccount(newAccount);
        }
    }

    /**
     * Gets account logs
     *
     * @param accountType
     * @param userId
     * @return
     */

    @AopServiceException
    public Collection<AccountLog> getAccountLogs(@Min(0) int accountType, @Min(0) long userId) {
        return accountRepository.getAccountLogs(accountType, userId);
    }

    /**
     * Creates a account batch
     *
     * @param accountBatch
     * @return
     */

    @AopServiceException
    public AccountBatch createAccountBatch(@NotNull AccountBatch accountBatch) {
        accountBatch.setCreateTime(LocalDateTime.now());
        accountBatch.setEditTime(LocalDateTime.now());
        return accountRepository.saveAccountBatch(accountBatch);
    }

    /**
     * Updates an account batch
     *
     * @param accountBatchPO
     * @return
     */

    @AopServiceException
    public AccountBatch updateAccountBatch(@NotNull AccountBatch accountBatch) {
        accountBatch.setEditTime(LocalDateTime.now());
        return accountRepository.saveAccountBatch(accountBatch);
    }

    /**
     * Creates an account log
     *
     * @param accountLog
     * @return
     */

    @AopServiceException
    public AccountLog createAccountLog(@NotNull AccountLog accountLog) {
        accountLog.setCreateTime(LocalDateTime.now());
        accountLog.setEditTime(LocalDateTime.now());
        return accountRepository.saveAccountLog(accountLog);
    }

    /**
     * Creates a batch of an account log
     *
     * @param accountLogBatchPO
     * @return
     */

    @AopServiceException
    public AccountBatchLog createAccountLogBatch(@NotNull AccountBatchLog accountBatchLog) {
        accountBatchLog.setCreateTime(LocalDateTime.now());
        accountBatchLog.setEditTime(LocalDateTime.now());
        return accountRepository.saveAccountLogBatch(accountBatchLog);
    }

    /**
     * Increases account balance
     *
     * @param accountType
     * @param userId
     * @param operatorId
     * @param operateDate
     * @param remark
     * @param amount
     * @return
     */

    @Transactional
    @AopServiceException
    public synchronized boolean deposit(@Min(0) int accountType, @Min(0) long userId, @Min(0) long operatorId, LocalDateTime operateDate, String remark, BigDecimal amount) {
        var account = getAccountByType(accountType, userId);
        if (Objects.isNull(account)) return false;

        var log = new AccountLog();
        log.setAccountId(account.getAccountId());
        log.setAccountType(accountType);
        log.setBatchManagement(account.isBatchManagement());
        log.setBeginningBalance(account.getBalance());
        log.setDebitCreditType(GlobalState.DEBIT_TYPE);
        log.setDebitAmount(amount);
        var totalAmount = account.getBalance().add(amount);
        log.setClosingBalance(totalAmount);
        log.setCreateTime(LocalDateTime.now());
        log.setEditTime(LocalDateTime.now());
        log.setOperateUser(operatorId);
        log.setReferenceRemark(remark);
        log.setUserId(userId);
        createAccountLog(log);

        account.setBalance(totalAmount);
        account.setEditTime(LocalDateTime.now());
        accountRepository.saveAccount(account);
        if (account.isBatchManagement()) {
            var batches = account.getAccountBatches();
            addToBatch(account.getAccountId(), accountType, userId, batches, operateDate, amount, log.getLogId());
        }
        return true;
    }

    private void addToBatch(@Min(0) long accountId, @Min(0) int accountType, @Min(0) long userId, @NotBlank Collection<AccountBatch> batches, LocalDateTime operateDate, @Min(0) BigDecimal amount, @Min(0) long logId) {
        // 按日期记录批次号，每日一个批次
        var batchNumber = DateTimeHelper.getDateTimeString(operateDate, "yyyyMMdd");
        for (AccountBatch batch : batches) {
            if (batch.getBatchNumber().equals(batchNumber)) {
                // 该批次记录已存在，则修改批次数量
                batch.setBalance(batch.getBalance().add(amount));
                batch.setEditTime(LocalDateTime.now());
                accountRepository.saveAccountBatch(batch);

                // 记录批次日志
                AccountBatchLog logBatch = new AccountBatchLog();
                logBatch.setAccountId(accountId);
                logBatch.setAccountType(accountType);
                logBatch.setBalance(amount);
                logBatch.setBatchNumber(batchNumber);
                logBatch.setCreateTime(LocalDateTime.now());
                logBatch.setEditTime(LocalDateTime.now());
                logBatch.setUserId(userId);
                logBatch.setLogId(logId);
                createAccountLogBatch(logBatch);
                return;
            }
        }

        // 如果该批次号尚不存在，则建立新批次
        var batch = new AccountBatch();
        batch.setAccountId(accountId);
        batch.setAccountType(accountType);
        batch.setUserId(userId);
        batch.setBalance(amount);
        batch.setCreateTime(LocalDateTime.now());
        batch.setEditTime(LocalDateTime.now());
        batch.setBatchNumber(batchNumber);
        createAccountBatch(batch);

        // 记录新批次日志
        var logBatch = new AccountBatchLog();
        logBatch.setAccountId(accountId);
        logBatch.setAccountType(accountType);
        logBatch.setBalance(amount);
        logBatch.setBatchNumber(batchNumber);
        logBatch.setCreateTime(LocalDateTime.now());
        logBatch.setEditTime(LocalDateTime.now());
        logBatch.setUserId(userId);
        logBatch.setLogId(logId);
        createAccountLogBatch(logBatch);
    }

    /**
     * Decreases account balance
     *
     * @param accountType
     * @param userId
     * @param operatorId
     * @param operateDate
     * @param remark
     * @param amount
     * @return
     */

    @Transactional
    @AopServiceException
    public synchronized boolean withdraw(@Min(0) int accountType, @Min(0) long userId, @Min(0) long operatorId, LocalDateTime operateDate, String remark, @Min(0) BigDecimal amount) {
        var account = getAccountByType(accountType, userId);
        if (account == null) return false;

        // 判断账户余额是否足够扣减
        if (account.getBalance().doubleValue() - amount.doubleValue() < 0) {
            var errorCode = "service-exception.account.balance.notenough";
            throw new ServiceException(null, errorCode, "account Type: " + accountType + " user id: " + userId + " amount: " + amount.doubleValue());
        }

        // 记录批次扣减日志
        var log = new AccountLog();
        log.setAccountId(account.getAccountId());
        log.setAccountType(accountType);
        log.setBatchManagement(account.isBatchManagement());
        log.setBeginningBalance(account.getBalance());
        log.setDebitCreditType(GlobalState.CREDIT_TYPE);
        log.setCreditAmount(amount);
        var totalAmount = account.getBalance().subtract(amount);
        log.setClosingBalance(totalAmount);
        log.setCreateTime(LocalDateTime.now());
        log.setEditTime(LocalDateTime.now());
        log.setOperateUser(operatorId);
        log.setReferenceRemark(remark);
        log.setUserId(userId);
        createAccountLog(log);

        account.setBalance(totalAmount);
        account.setEditTime(LocalDateTime.now());
        accountRepository.saveAccount(account);

        if (account.isBatchManagement()) {
            Collection<AccountBatch> batches = account.getAccountBatches();
            subtractBatch(account.getAccountId(), accountType, userId, batches, operateDate, amount, log.getLogId());
        }
        return true;
    }

    private void subtractBatch(@Min(0) long accountId, @Min(0) int accountType, @Min(0) long userId, @NotBlank Collection<AccountBatch> batches, LocalDateTime operateDate, @Min(0) BigDecimal amount, @Min(0) long logId) {
        var currentAmount = amount.add(new BigDecimal(0));
        for (var batch : batches) {
            var batchAmount = batch.getBalance();
            if (batchAmount.doubleValue() == 0) continue;
            // 循环扣减每个批次数量，直到扣减完成
            if (currentAmount.doubleValue() > 0) {
                if (batchAmount.doubleValue() > currentAmount.doubleValue()) {
                    batch.setBalance(batchAmount.subtract(currentAmount));
                    accountRepository.saveAccountBatch(batch);

                    // 记录批次扣减日志
                    var logBatch = new AccountBatchLog();
                    logBatch.setAccountId(accountId);
                    logBatch.setAccountType(accountType);
                    logBatch.setBalance(currentAmount);
                    logBatch.setBatchNumber(batch.getBatchNumber());
                    logBatch.setCreateTime(operateDate);
                    logBatch.setEditTime(operateDate);
                    logBatch.setUserId(userId);
                    logBatch.setLogId(logId);
                    createAccountLogBatch(logBatch);
                    currentAmount = new BigDecimal(0);
                } else {
                    currentAmount = currentAmount.subtract(batch.getBalance());
                    var oldBatchBalance = batch.getBalance().add(new BigDecimal(0));
                    batch.setBalance(new BigDecimal(0));
                    accountRepository.saveAccountBatch(batch);
                    // 记录批次扣减日志
                    var logBatch = new AccountBatchLog();
                    logBatch.setAccountId(accountId);
                    logBatch.setAccountType(accountType);
                    logBatch.setBalance(oldBatchBalance);
                    logBatch.setBatchNumber(batch.getBatchNumber());
                    logBatch.setCreateTime(LocalDateTime.now());
                    logBatch.setEditTime(LocalDateTime.now());
                    logBatch.setUserId(userId);
                    logBatch.setLogId(logId);
                    createAccountLogBatch(logBatch);
                }
            } else {
                break;
            }
        }
    }


}
